Оконные функции — одна из самых мощных возможностей SQL для выполнения сложных аналитических вычислений. В отличие от агрегатных функций, которые схлопывают несколько строк в единый результат, оконные функции позволяют выполнять вычисления по набору строк, связанных с текущей строкой — при этом сохраняя все отдельные строки в результате запроса.
Этот урок знакомит с фундаментальными концепциями оконных функций и демонстрирует, как они могут трансформировать ваши возможности анализа данных.
Оконная функция выполняет вычисление по набору строк таблицы, которые каким-то образом связаны с текущей строкой. Этот набор строк называется "окном" или "оконным фреймом". Ключевое отличие от обычных агрегатных функций заключается в том, что оконные функции не группируют строки в единую выходную строку — каждая строка сохраняет свою идентичность.
Представьте это как взгляд через движущееся окно, пока вы просматриваете свои данные. Для каждой строки вы можете видеть и вычислять значения на основе связанных строк вокруг неё, но каждая строка все равно появляется отдельно в результате.
Ключевые характеристики:
OVERОбщий синтаксис оконной функции:
имя_оконной_функции(выражение) OVER (
[PARTITION BY выражение_partиции]
[ORDER BY выражение_сортировки]
[предложение_оконного_фрейма]
)
Компоненты:
ROW_NUMBER, SUM, AVG)Начнём с одной из наиболее часто используемых оконных функций: ROW_NUMBER(). Эта функция присваивает уникальный последовательный номер каждой строке внутри партиции.
SELECT
payment_id,
customer_id,
amount,
payment_date,
ROW_NUMBER() OVER (ORDER BY payment_date) AS row_num
FROM
payment
LIMIT 10;
Этот запрос присваивает последовательный номер каждому платежу, упорядоченному по дате платежа. Предложение OVER (ORDER BY payment_date) указывает SQL:
payment_dateИстинная мощь оконных функций проявляется, когда вы используете PARTITION BY для создания отдельных окон для разных групп:
SELECT
customer_id,
amount,
payment_date,
ROW_NUMBER() OVER (
PARTITION BY customer_id
ORDER BY payment_date
) AS payment_number
FROM
payment
WHERE
customer_id IN (1, 2, 3)
ORDER BY
customer_id,
payment_date;
Вот что происходит:
PARTITION BY customer_id создаёт отдельное окно для каждого клиентаpayment_dateROW_NUMBER() начинает отсчёт с 1 для каждого нового клиентаВизуализация:
Клиент 1: Клиент 2: Клиент 3:
Строка 1 ----\ Строка 1 ----\ Строка 1 ----\
Строка 2 -----\ Строка 2 -----\ Строка 2 -----\
Строка 3 ------\Строка 3 ------\Строка 3 ------\
... ... ...
Каждый клиент имеет свою независимую нумерацию строк.
Оконные функции упрощают идентификацию самой последней записи в каждой группе:
WITH numbered_payments AS (
SELECT
customer_id,
amount,
payment_date,
ROW_NUMBER() OVER (
PARTITION BY customer_id
ORDER BY payment_date DESC
) AS recency_rank
FROM
payment
)
SELECT
customer_id,
amount,
payment_date
FROM
numbered_payments
WHERE
recency_rank = 1
ORDER BY
customer_id
LIMIT 10;
Этот запрос находит самый последний платёж для каждого клиента:
recency_rank = 1 (самый последний)Оконные функции также могут выполнять агрегацию, сохраняя отдельные строки:
SELECT
customer_id,
amount,
payment_date,
SUM(amount) OVER (PARTITION BY customer_id) AS total_spent,
AVG(amount) OVER (PARTITION BY customer_id) AS avg_payment,
amount - AVG(amount) OVER (PARTITION BY customer_id) AS diff_from_avg
FROM
payment
WHERE
customer_id IN (1, 2, 3)
ORDER BY
customer_id,
payment_date;
Для каждого платежа этот запрос показывает:
Обратите внимание, как обычные агрегатные функции требовали бы GROUP BY и схлопывали строки, но оконные функции позволяют сохранить все детали, добавляя агрегированный контекст.
Важно понимать разницу:
GROUP BY (Агрегатные функции):
SELECT
customer_id,
COUNT(*) AS payment_count,
SUM(amount) AS total_amount
FROM
payment
GROUP BY
customer_id;
Результат: Одна строка на клиента
Оконные функции:
SELECT
customer_id,
payment_id,
amount,
COUNT(*) OVER (PARTITION BY customer_id) AS payment_count,
SUM(amount) OVER (PARTITION BY customer_id) AS total_amount
FROM
payment;
Результат: Каждая строка платежа сохранена, с агрегированными значениями, добавленными как дополнительные столбцы
ROW_NUMBER()).GROUP BY, оконные функции не схлопывают строки — они добавляют вычисленные столбцы к вашим существующим данным.В следующих уроках мы изучим больше оконных функций, таких как RANK(), DENSE_RANK(), NTILE(), и углубимся в оконные фреймы и продвинутые аналитические вычисления.